import { Analyzer } from '../Analyzer';
import { Node } from './Node';
import { Edge } from './Edge';
import { stringify } from 'flatted';

export class CFGHandler {
    private readonly analyzer: Analyzer;
    private nodes: Node[] | undefined;
    private edges: Edge[] | undefined;

    public constructor(directory: string) {
        this.analyzer = new Analyzer(directory);
    }

    public generateCallGraph() {
        this.analyzer.generateCallGraph();
    }

    public dumpCallGraph(path: string) {
        this.analyzer.dumpCallGraph(path);
    }

    public run() {
        const nodes: Node[] = [];
        const edges: Edge[] = [];
        const scene = this.analyzer.getScene();
        for (const arkFile of scene.getFiles()) {
            for (const arkClass of arkFile.getClasses()) {
                for (const arkMethod of arkClass.getMethods()) {
                    if (arkMethod.getName() == '_DEFAULT_ARK_METHOD') {
                        continue;
                    }
                    const json = stringify(arkMethod);
                    const node = new Node(json);
                    node.setArkMethod(arkMethod);
                    nodes.push(node);
                }
                for (const arkMethod of arkClass.getMethods()) {
                    const body = arkMethod.getBody();
                    if (body !== undefined) {
                        const blocks = [...body.getCfg().getBlocks()];
                        for (const block of blocks) {
                            for (const next of block.getSuccessors()) {
                                const type: string = "CFG";
                                const start = block.getStmts()[0];
                                const end = next.getStmts()[0];
                                const edge = new Edge(start, end, type);
                                edges.push(edge);
                            }
                            for (let i = 0; i < block.getStmts().length; ++i) {
                                const stmt = block.getStmts()[i];
                                const next = block.getStmts()[i + 1];
                                if (next !== undefined) {
                                    const type: string = "next";
                                    const edge = new Edge(stmt, next, type);
                                    edges.push(edge);
                                }
                                const attribute = stringify(stmt);
                                const node = new Node(attribute);
                                node.setStmt(stmt);
                                nodes.push(node);
                                for (const edge of edges) {
                                    if (edge.getStart() === stmt) {
                                        node.addFromEdge(edge);
                                    }
                                    if (edge.getEnd() === stmt) {
                                        node.addToEdge(edge);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        this.edges = edges;
        this.nodes = nodes;
    }

    public getEdges() {
        return this.edges;
    }

    public getNodes() {
        return this.nodes;
    }
}
